/**
 * jQuery TextExt Plugin
 * http://textextjs.com
 *
 * @version 1.3.1
 * @copyright Copyright (C) 2011 Alex Gorbatchev. All rights reserved.
 * @license MIT License
 */
( function( $, undefined )
{
    /**
     * TextExt is the main core class which by itself doesn't provide any functionality
     * that is user facing, however it has the underlying mechanics to bring all the
     * plugins together under one roof and make them work with each other or on their
     * own.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt
     */
    function TextExt() {
    }
    ;

    /**
     * ItemManager is used to seamlessly convert between string that come from the user input to whatever 
     * the format the item data is being passed around in. It's used by all plugins that in one way or 
     * another operate with items, such as Tags, Filter, Autocomplete and Suggestions. Default implementation 
     * works with `String` type. 
     *
     * Each instance of `TextExt` creates a new instance of default implementation of `ItemManager`
     * unless `itemManager` option was set to another implementation.
     *
     * To satisfy requirements of managing items of type other than a `String`, different implementation
     * if `ItemManager` should be supplied.
     *
     * If you wish to bring your own implementation, you need to create a new class and implement all the 
     * methods that `ItemManager` has. After, you need to supply your pass via the `itemManager` option during
     * initialization like so:
     *
     *     $('#input').textext({
     *         itemManager : CustomItemManager
     *     })
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager
     */
    function ItemManager() {
    }
    ;

    /**
     * TextExtPlugin is a base class for all plugins. It provides common methods which are reused
     * by majority of plugins.
     *
     * All plugins must register themselves by calling the `$.fn.textext.addPlugin(name, constructor)`
     * function while providing plugin name and constructor. The plugin name is the same name that user
     * will identify the plugin in the `plugins` option when initializing TextExt component and constructor
     * function will create a new instance of the plugin. *Without registering, the core won't
     * be able to see the plugin.*
     *
     * <span class="new label version">new in 1.2.0</span> You can get instance of each plugin from the core 
     * via associated function with the same name as the plugin. For example:
     *
     *     $('#input').textext()[0].tags()
     *     $('#input').textext()[0].autocomplete()
     *     ...
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin
     */
    function TextExtPlugin() {
    }
    ;

    var stringify = ( JSON || { } ).stringify,
        slice = Array.prototype.slice,
        p,
        UNDEFINED = 'undefined',
        /**
         * TextExt provides a way to pass in the options to configure the core as well as
         * each plugin that is being currently used. The jQuery exposed plugin `$().textext()` 
         * function takes a hash object with key/value set of options. For example:
         *
         *     $('textarea').textext({
         *         enabled: true
         *     })
         *
         * There are multiple ways of passing in the options:
         *
         * 1. Options could be nested multiple levels deep and accessed using all lowercased, dot
         * separated style, eg `foo.bar.world`. The manual is using this style for clarity and
         * consistency. For example:
         *
         *        {
         *            item: {
         *                manager: ...
         *            },
         *
         *            html: {
         *                wrap: ...
         *            },
         *
         *            autocomplete: {
         *                enabled: ...,
         *                dropdown: {
         *                   position: ...
         *                }
         *            }
         *        }
         *
         * 2. Options could be specified using camel cased names in a flat key/value fashion like so:
         *
         *        {
         *            itemManager: ...,
         *            htmlWrap: ...,
         *            autocompleteEnabled: ...,
         *            autocompleteDropdownPosition: ...
         *        }
         *
         * 3. Finally, options could be specified in mixed style. It's important to understand that
         * for each dot separated name, its alternative in camel case is also checked for, eg for 
         * `foo.bar.world` it's alternatives could be `fooBarWorld`, `foo.barWorld` or `fooBar.world`, 
         * which translates to `{ foo: { bar: { world: ... } } }`, `{ fooBarWorld: ... }`, 
         * `{ foo : { barWorld : ... } }` or `{ fooBar: { world: ... } }` respectively. For example:
         *
         *        {
         *            itemManager : ...,
         *            htmlWrap: ...,
         *            autocomplete: {
         *                enabled: ...,
         *                dropdownPosition: ...
         *            }
         *        }
         *
         * Mixed case is used through out the code, wherever it seems appropriate. However in the code, all option
         * names are specified in the dot notation because it works both ways where as camel case is not
         * being converted to its alternative dot notation.
         *
         * @author agorbatchev
         * @date 2011/08/17
         * @id TextExt.options
         */

        /**
         * Default instance of `ItemManager` which takes `String` type as default for tags.
         *
         * @name item.manager
         * @default ItemManager
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.options.item.manager
         */
        OPT_ITEM_MANAGER = 'item.manager',
        /**
         * List of plugins that should be used with the current instance of TextExt. The list could be
         * specified as array of strings or as comma or space separated string.
         *
         * @name plugins
         * @default []
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.options.plugins
         */
        OPT_PLUGINS = 'plugins',
        /**
         * TextExt allows for overriding of virtually any method that the core or any of its plugins
         * use. This could be accomplished through the use of the `ext` option.
         *
         * It's possible to specifically target the core or any plugin, as well as overwrite all the
         * desired methods everywhere.
         *
         * 1. Targeting the core:
         *
         *        ext: {
         *            core: {
         *                trigger: function()
         *                {
         *                    console.log('TextExt.trigger', arguments);
         *                    $.fn.textext.TextExt.prototype.trigger.apply(this, arguments);
         *                }
         *            }
         *        }
         *
         * 2. Targeting individual plugins:
         *
         *        ext: {
         *            tags: {
         *                addTags: function(tags)
         *                {
         *                    console.log('TextExtTags.addTags', tags);
         *                    $.fn.textext.TextExtTags.prototype.addTags.apply(this, arguments);
         *                }
         *            }
         *        }
         *
         * 3. Targeting `ItemManager` instance:
         *
         *        ext: {
         *            itemManager: {
         *                stringToItem: function(str)
         *                {
         *                    console.log('ItemManager.stringToItem', str);
         *                    return $.fn.textext.ItemManager.prototype.stringToItem.apply(this, arguments);
         *                }
         *            }
         *        }
         *
         * 4. And finally, in edge cases you can extend everything at once:
         *
         *        ext: {
         *            '*': {
         *                fooBar: function() {}
         *            }
         *        }
         *
         * @name ext
         * @default {}
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.options.ext
         */
        OPT_EXT = 'ext',
        /**
         * HTML source that is used to generate elements necessary for the core and all other
         * plugins to function.
         *
         * @name html.wrap
         * @default '<div class="text-core"><div class="text-wrap"/></div>'
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.options.html.wrap
         */
        OPT_HTML_WRAP = 'html.wrap',
        /**
         * HTML source that is used to generate hidden input value of which will be submitted 
         * with the HTML form.
         *
         * @name html.hidden
         * @default '<input type="hidden" />'
         * @author agorbatchev
         * @date 2011/08/20
         * @id TextExt.options.html.hidden
         */
        OPT_HTML_HIDDEN = 'html.hidden',
        /**
         * Hash table of key codes and key names for which special events will be created
         * by the core. For each entry a `[name]KeyDown`, `[name]KeyUp` and `[name]KeyPress` events 
         * will be triggered along side with `anyKeyUp` and `anyKeyDown` events for every 
         * key stroke.
         *
         * Here's a list of default keys:
         *
         *     {
         *         8   : 'backspace',
         *         9   : 'tab',
         *         13  : 'enter!',
         *         27  : 'escape!',
         *         37  : 'left',
         *         38  : 'up!',
         *         39  : 'right',
         *         40  : 'down!',
         *         46  : 'delete',
         *         108 : 'numpadEnter'
         *     }
         *
         * Please note the `!` at the end of some keys. This tells the core that by default
         * this keypress will be trapped and not passed on to the text input.
         *
         * @name keys
         * @default { ... }
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.options.keys
         */
        OPT_KEYS = 'keys',
        /**
         * The core triggers or reacts to the following events.
         *
         * @author agorbatchev
         * @date 2011/08/17
         * @id TextExt.events
         */

        /**
         * Core triggers `preInvalidate` event before the dimensions of padding on the text input
         * are set.
         *
         * @name preInvalidate
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.preInvalidate
         */
        EVENT_PRE_INVALIDATE = 'preInvalidate',
        /**
         * Core triggers `postInvalidate` event after the dimensions of padding on the text input
         * are set.
         *
         * @name postInvalidate
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.postInvalidate
         */
        EVENT_POST_INVALIDATE = 'postInvalidate',
        /**
         * Core triggers `getFormData` on every key press to collect data that will be populated
         * into the hidden input that will be submitted with the HTML form and data that will
         * be displayed in the input field that user is currently interacting with.
         *
         * All plugins that wish to affect how the data is presented or sent must react to 
         * `getFormData` and populate the data in the following format:
         *
         *     {
         *         input : {String},
         *         form  : {Object}
         *     }
         *
         * The data key must be a numeric weight which will be used to determine which data
         * ends up being used. Data with the highest numerical weight gets the priority. This
         * allows plugins to set the final data regardless of their initialization order, which
         * otherwise would be impossible.
         *
         * For example, the Tags and Autocomplete plugins have to work side by side and Tags
         * plugin must get priority on setting the data. Therefore the Tags plugin sets data
         * with the weight 200 where as the Autocomplete plugin sets data with the weight 100.
         *
         * Here's an example of a typical `getFormData` handler:
         * 
         *     TextExtPlugin.prototype.onGetFormData = function(e, data, keyCode)
         *     {
         *         data[100] = self.formDataObject('input value', 'form value');
         *     };
         *
         * Core also reacts to the `getFormData` and updates hidden input with data which will be
         * submitted with the HTML form.
         *
         * @name getFormData
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.getFormData
         */
        EVENT_GET_FORM_DATA = 'getFormData',
        /**
         * Core triggers and reacts to the `setFormData` event to update the actual value in the
         * hidden input that will be submitted with the HTML form. Second argument can be value
         * of any type and by default it will be JSON serialized with `TextExt.serializeData()`
         * function.
         *
         * @name setFormData
         * @author agorbatchev
         * @date 2011/08/22
         * @id TextExt.events.setFormData
         */
        EVENT_SET_FORM_DATA = 'setFormData',
        /**
         * Core triggers and reacts to the `setInputData` event to update the actual value in the
         * text input that user is interacting with. Second argument must be of a `String` type
         * the value of which will be set into the text input.
         *
         * @name setInputData
         * @author agorbatchev
         * @date 2011/08/22
         * @id TextExt.events.setInputData
         */
        EVENT_SET_INPUT_DATA = 'setInputData',
        /**
         * Core triggers `postInit` event to let plugins run code after all plugins have been 
         * created and initialized. This is a good place to set some kind of global values before 
         * somebody gets to use them. This is not the right place to expect all plugins to finish
         * their initialization.
         *
         * @name postInit
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.postInit
         */
        EVENT_POST_INIT = 'postInit',
        /**
         * Core triggers `ready` event after all global configuration and prepearation has been
         * done and the TextExt component is ready for use. Event handlers should expect all 
         * values to be set and the plugins to be in the final state.
         *
         * @name ready
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.ready
         */
        EVENT_READY = 'ready',
        /**
         * Core triggers `anyKeyUp` event for every key up event triggered within the component.
         *
         * @name anyKeyUp
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.anyKeyUp
         */

        /**
         * Core triggers `anyKeyDown` event for every key down event triggered within the component.
         *
         * @name anyKeyDown
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.anyKeyDown
         */

        /**
         * Core triggers `[name]KeyUp` event for every key specifid in the `keys` option that is 
         * triggered within the component.
         *
         * @name [name]KeyUp
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.[name]KeyUp
         */

        /**
         * Core triggers `[name]KeyDown` event for every key specified in the `keys` option that is 
         * triggered within the component.
         *
         * @name [name]KeyDown
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.[name]KeyDown
         */

        /**
         * Core triggers `[name]KeyPress` event for every key specified in the `keys` option that is 
         * triggered within the component.
         *
         * @name [name]KeyPress
         * @author agorbatchev
         * @date 2011/08/19
         * @id TextExt.events.[name]KeyPress
         */

        DEFAULT_OPTS = {
            itemManager: ItemManager,
            plugins: [ ],
            ext: { },
            html: {
                wrap: '<div class="text-core"><div class="text-wrap"/></div>',
                hidden: '<input type="hidden" />'
            },
            keys: {
                8: 'backspace',
                9: 'enter!', //'tab!',
                13: 'enter!',
                27: 'escape!',
                37: 'left',
                38: 'up!',
                39: 'right',
                40: 'down!',
                46: 'delete',
                108: 'numpadEnter'
            }
        }
    ;

    // Freak out if there's no JSON.stringify function found
    if ( !stringify )
        throw new Error( 'JSON.stringify() not found' );

    /**
     * Returns object property by name where name is dot-separated and object is multiple levels deep.
     * @param target Object Source object.
     * @param name String Dot separated property name, ie `foo.bar.world`
     * @id core.getProperty
     */
    function getProperty( source, name )
    {
        if ( typeof ( name ) === 'string' )
            name = name.split( '.' );

        var fullCamelCaseName = name.join( '.' ).replace( /\.(\w)/g, function( match, letter ) {
            return letter.toUpperCase()
        } ),
            nestedName = name.shift(),
            result
            ;

        if ( typeof ( result = source[fullCamelCaseName] ) != UNDEFINED )
            result = result;

        else if ( typeof ( result = source[nestedName] ) != UNDEFINED && name.length > 0 )
            result = getProperty( result, name );

        // name.length here should be zero
        return result;
    }
    ;

    /**
     * Hooks up specified events in the scope of the current object.
     * @author agorbatchev
     * @date 2011/08/09
     */
    function hookupEvents()
    {
        var args = slice.apply( arguments ),
            self = this,
            target = args.length === 1 ? self : args.shift(),
            event
            ;

        args = args[0] || { };

        function bind( event, handler )
        {
            target.bind( event, function()
            {
                // apply handler to our PLUGIN object, not the target
                return handler.apply( self, arguments );
            } );
        }

        for ( event in args )
            bind( event, args[event] );
    }
    ;

    function formDataObject( input, form )
    {
        return { 'input': input, 'form': form };
    }
    ;

    //--------------------------------------------------------------------------------
    // ItemManager core component

    p = ItemManager.prototype;

    /**
     * Initialization method called by the core during instantiation.
     *
     * @signature ItemManager.init(core)
     *
     * @param core {TextExt} Instance of the TextExt core class.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.init
     */
    p.init = function( core )
    {
    };

    /**
     * Filters out items from the list that don't match the query and returns remaining items. Default 
     * implementation checks if the item starts with the query.
     *
     * @signature ItemManager.filter(list, query)
     *
     * @param list {Array} List of items. Default implementation works with strings.
     * @param query {String} Query string.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.filter
     */
    p.filter = function( list, query )
    {
        var result = [ ],
            i, item
            ;

        for ( i = 0; i < list.length; i++ )
        {
            item = list[i];
            if ( this.itemContains( item, query ) )
                result.push( item );
        }

        return result;
    };

    /**
     * Returns `true` if specified item contains another string, `false` otherwise. In the default implementation 
     * `String.indexOf()` is used to check if item string begins with the needle string.
     *
     * @signature ItemManager.itemContains(item, needle)
     *
     * @param item {Object} Item to check. Default implementation works with strings.
     * @param needle {String} Search string to be found within the item.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.itemContains
     */
    p.itemContains = function( item, needle )
    {
        return this.itemToString( item ).toLowerCase().indexOf( needle.toLowerCase() ) == 0;
    };

    /**
     * Converts specified string to item. Because default implemenation works with string, input string
     * is simply returned back. To use custom objects, different implementation of this method could
     * return something like `{ name : {String} }`.
     *
     * @signature ItemManager.stringToItem(str)
     *
     * @param str {String} Input string.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.stringToItem
     */
    p.stringToItem = function( str )
    {
        return str;
    };

    /**
     * Converts specified item to string. Because default implemenation works with string, input string
     * is simply returned back. To use custom objects, different implementation of this method could
     * for example return `name` field of `{ name : {String} }`.
     *
     * @signature ItemManager.itemToString(item)
     *
     * @param item {Object} Input item to be converted to string.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.itemToString
     */
    p.itemToString = function( item )
    {
        return item;
    };

    /**
     * Returns `true` if both items are equal, `false` otherwise. Because default implemenation works with 
     * string, input items are compared as strings. To use custom objects, different implementation of this 
     * method could for example compare `name` fields of `{ name : {String} }` type object.
     *
     * @signature ItemManager.compareItems(item1, item2)
     *
     * @param item1 {Object} First item.
     * @param item2 {Object} Second item.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id ItemManager.compareItems
     */
    p.compareItems = function( item1, item2 )
    {
        return item1 == item2;
    };

    //--------------------------------------------------------------------------------
    // TextExt core component

    p = TextExt.prototype;

    /**
     * Initializes current component instance with work with the supplied text input and options.
     *
     * @signature TextExt.init(input, opts)
     *
     * @param input {HTMLElement} Text input.
     * @param opts {Object} Options.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.init
     */
    p.init = function( input, opts )
    {
        var self = this,
            hiddenInput,
            itemManager,
            container
            ;

        self._defaults = $.extend( { }, DEFAULT_OPTS );
        self._opts = opts || { };
        self._plugins = { };
        self._itemManager = itemManager = new ( self.opts( OPT_ITEM_MANAGER ) )();
        input = $( input );
        container = $( self.opts( OPT_HTML_WRAP ) );
        hiddenInput = $( self.opts( OPT_HTML_HIDDEN ) );

        input
            .wrap( container )
            .keydown( function( e ) {
                return self.onKeyDown( e )
            } )
            .keyup( function( e ) {
                return self.onKeyUp( e )
            } )
            .data( 'textext', self )
            ;

        // keep references to html elements using jQuery.data() to avoid circular references
        $( self ).data( {
            'hiddenInput': hiddenInput,
            'wrapElement': input.parents( '.text-wrap' ).first(),
            'input': input
        } );

        // set the name of the hidden input to the text input's name
        hiddenInput.attr( 'name', input.attr( 'name' ) );
        // remove name attribute from the text input
        input.attr( 'name', null );
        // add hidden input to the DOM
        hiddenInput.insertAfter( input );

        $.extend( true, itemManager, self.opts( OPT_EXT + '.item.manager' ) );
        $.extend( true, self, self.opts( OPT_EXT + '.*' ), self.opts( OPT_EXT + '.core' ) );

        self.originalWidth = input.outerWidth();

        self.invalidateBounds();

        itemManager.init( self );

        self.initPatches();
        self.initPlugins( self.opts( OPT_PLUGINS ), $.fn.textext.plugins );

        self.on( {
            setFormData: self.onSetFormData,
            getFormData: self.onGetFormData,
            setInputData: self.onSetInputData,
            anyKeyUp: self.onAnyKeyUp
        } );

        self.trigger( EVENT_POST_INIT );
        self.trigger( EVENT_READY );

        self.getFormData( 0 );
    };

    /**
     * Initialized all installed patches against current instance. The patches are initialized based on their
     * initialization priority which is returned by each patch's `initPriority()` method. Priority
     * is a `Number` where patches with higher value gets their `init()` method called before patches
     * with lower priority value.
     *
     * This facilitates initializing of patches in certain order to insure proper dependencies
     * regardless of which order they are loaded.
     *
     * By default all patches have the same priority - zero, which means they will be initialized
     * in rorder they are loaded, that is unless `initPriority()` is overriden.
     *
     * @signature TextExt.initPatches()
     *
     * @author agorbatchev
     * @date 2011/10/11
     * @id TextExt.initPatches
     */
    p.initPatches = function()
    {
        var list = [ ],
            source = $.fn.textext.patches,
            name
            ;

        for ( name in source )
            list.push( name );

        this.initPlugins( list, source );
    };

    /**
     * Creates and initializes all specified plugins. The plugins are initialized based on their
     * initialization priority which is returned by each plugin's `initPriority()` method. Priority
     * is a `Number` where plugins with higher value gets their `init()` method called before plugins
     * with lower priority value.
     *
     * This facilitates initializing of plugins in certain order to insure proper dependencies
     * regardless of which order user enters them in the `plugins` option field.
     *
     * By default all plugins have the same priority - zero, which means they will be initialized
     * in the same order as entered by the user.
     *
     * @signature TextExt.initPlugins(plugins)
     *
     * @param plugins {Array} List of plugin names to initialize.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.initPlugins
     */
    p.initPlugins = function( plugins, source )
    {
        var self = this,
            ext, name, plugin, initList = [ ], i
            ;

        if ( typeof ( plugins ) == 'string' )
            plugins = plugins.split( /\s*,\s*|\s+/g );

        for ( i = 0; i < plugins.length; i++ )
        {
            name = plugins[i];
            plugin = source[name];

            if ( plugin )
            {
                self._plugins[name] = plugin = new plugin();
                self[name] = ( function( plugin ) {
                    return function() {
                        return plugin;
                    }
                } )( plugin );
                initList.push( plugin );
                $.extend( true, plugin, self.opts( OPT_EXT + '.*' ), self.opts( OPT_EXT + '.' + name ) );
            }
        }

        // sort plugins based on their priority values
        initList.sort( function( p1, p2 )
        {
            p1 = p1.initPriority();
            p2 = p2.initPriority();

            return p1 === p2
                ? 0
                : p1 < p2 ? 1 : -1
                ;
        } );

        for ( i = 0; i < initList.length; i++ )
            initList[i].init( self );
    };

    /**
     * Returns true if specified plugin is was instantiated for the current instance of core.
     *
     * @signature TextExt.hasPlugin(name)
     *
     * @param name {String} Name of the plugin to check.
     *
     * @author agorbatchev
     * @date 2011/12/28
     * @id TextExt.hasPlugin
     * @version 1.1
     */
    p.hasPlugin = function( name )
    {
        return !!this._plugins[name];
    };

    /**
     * Allows to add multiple event handlers which will be execued in the scope of the current object.
     * 
     * @signature TextExt.on([target], handlers)
     *
     * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
     *                        Handler function will still be executed in the current object's scope.
     * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.on
     */
    p.on = hookupEvents;

    /**
     * Binds an event handler to the input box that user interacts with.
     *
     * @signature TextExt.bind(event, handler)
     *
     * @param event {String} Event name.
     * @param handler {Function} Event handler.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.bind
     */
    p.bind = function( event, handler )
    {
        this.input().bind( event, handler );
    };

    /**
     * Triggers an event on the input box that user interacts with. All core events are originated here.
     * 
     * @signature TextExt.trigger(event, ...args)
     *
     * @param event {String} Name of the event to trigger.
     * @param ...args All remaining arguments will be passed to the event handler.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.trigger
     */
    p.trigger = function()
    {
        var args = arguments;
        this.input().trigger( args[0], slice.call( args, 1 ) );
    };

    /**
     * Returns instance of `itemManager` that is used by the component.
     *
     * @signature TextExt.itemManager()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.itemManager
     */
    p.itemManager = function()
    {
        return this._itemManager;
    };

    /**
     * Returns jQuery input element with which user is interacting with.
     *
     * @signature TextExt.input()
     *
     * @author agorbatchev
     * @date 2011/08/10
     * @id TextExt.input
     */
    p.input = function()
    {
        return $( this ).data( 'input' );
    };

    /**
     * Returns option value for the specified option by name. If the value isn't found in the user
     * provided options, it will try looking for default value.
     *
     * @signature TextExt.opts(name)
     *
     * @param name {String} Option name as described in the options.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.opts
     */
    p.opts = function( name )
    {
        var result = getProperty( this._opts, name );
        return typeof ( result ) == 'undefined' ? getProperty( this._defaults, name ) : result;
    };

    /**
     * Returns HTML element that was created from the `html.wrap` option. This is the top level HTML
     * container for the text input with which user is interacting with.
     *
     * @signature TextExt.wrapElement()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.wrapElement
     */
    p.wrapElement = function()
    {
        return $( this ).data( 'wrapElement' );
    };

    /**
     * Updates container to match dimensions of the text input. Triggers `preInvalidate` and `postInvalidate`
     * events.
     *
     * @signature TextExt.invalidateBounds()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.invalidateBounds
     */
    p.invalidateBounds = function()
    {
        var self = this,
            input = self.input(),
            wrap = self.wrapElement(),
            container = wrap.parent(),
            width = self.originalWidth + 'px',
            height
            ;

        self.trigger( EVENT_PRE_INVALIDATE );

        height = input.outerHeight() + 'px';

        // using css() method instead of width() and height() here because they don't seem to do the right thing in jQuery 1.8.x
        // https://github.com/alexgorbatchev/jquery-textext/issues/74
        input.css( { 'width': width } );
        wrap.css( { 'width': width, 'height': height } );
        container.css( { 'height': height } );

        self.trigger( EVENT_POST_INVALIDATE );
    };

    /**
     * Focuses user input on the text box.
     *
     * @signature TextExt.focusInput()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.focusInput
     */
    p.focusInput = function()
    {
        this.input()[0].focus();
    };

    /**
     * Serializes data for to be set into the hidden input field and which will be submitted 
     * with the HTML form.
     *
     * By default simple JSON serialization is used. It's expected that `JSON.stringify`
     * method would be available either through built in class in most modern browsers
     * or through JSON2 library.
     *
     * @signature TextExt.serializeData(data)
     *
     * @param data {Object} Data to serialize.
     *
     * @author agorbatchev
     * @date 2011/08/09
     * @id TextExt.serializeData
     */
    p.serializeData = stringify;

    /**
     * Returns the hidden input HTML element which will be submitted with the HTML form.
     *
     * @signature TextExt.hiddenInput()
     *
     * @author agorbatchev
     * @date 2011/08/09
     * @id TextExt.hiddenInput
     */
    p.hiddenInput = function( value )
    {
        return $( this ).data( 'hiddenInput' );
    };

    /**
     * Abstracted functionality to trigger an event and get the data with maximum weight set by all
     * the event handlers. This functionality is used for the `getFormData` event.
     *
     * @signature TextExt.getWeightedEventResponse(event, args)
     *
     * @param event {String} Event name.
     * @param args {Object} Argument to be passed with the event.
     *
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExt.getWeightedEventResponse
     */
    p.getWeightedEventResponse = function( event, args )
    {
        var self = this,
            data = { },
            maxWeight = 0
            ;

        self.trigger( event, data, args );

        for ( var weight in data )
            maxWeight = Math.max( maxWeight, weight );

        return data[maxWeight];
    };

    /**
     * Triggers the `getFormData` event to get all the plugins to return their data.
     *
     * After the data is returned, triggers `setFormData` and `setInputData` to update appopriate values.
     *
     * @signature TextExt.getFormData(keyCode)
     *
     * @param keyCode {Number} Key code number which has triggered this update. It's impotant to pass
     * this value to the plugins because they might return different values based on the key that was 
     * pressed. For example, the Tags plugin returns an empty string for the `input` value if the enter
     * key was pressed, otherwise it returns whatever is currently in the text input.
     *
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExt.getFormData
     */
    p.getFormData = function( keyCode )
    {
        var self = this,
            data = self.getWeightedEventResponse( EVENT_GET_FORM_DATA, keyCode || 0 )
            ;

        self.trigger( EVENT_SET_FORM_DATA, data['form'] );
        self.trigger( EVENT_SET_INPUT_DATA, data['input'] );
    };

    //--------------------------------------------------------------------------------
    // Event handlers

    /**
     * Reacts to the `anyKeyUp` event and triggers the `getFormData` to change data that will be submitted
     * with the form. Default behaviour is that everything that is typed in will be JSON serialized, so
     * the end result will be a JSON string.
     *
     * @signature TextExt.onAnyKeyUp(e)
     *
     * @param e {Object} jQuery event.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.onAnyKeyUp
     */
    p.onAnyKeyUp = function( e, keyCode )
    {
        if ( keyCode == 9 ) {
            keyCode = 13;
        }
        this.getFormData( keyCode );
    };

    /**
     * Reacts to the `setInputData` event and populates the input text field that user is currently
     * interacting with.
     *
     * @signature TextExt.onSetInputData(e, data)
     *
     * @param e {Event} jQuery event.
     * @param data {String} Value to be set.
     *
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExt.onSetInputData
     */
    p.onSetInputData = function( e, data )
    {
        this.input().val( data );
    };

    /**
     * Reacts to the `setFormData` event and populates the hidden input with will be submitted with
     * the HTML form. The value will be serialized with `serializeData()` method.
     *
     * @signature TextExt.onSetFormData(e, data)
     *
     * @param e {Event} jQuery event.
     * @param data {Object} Data that will be set.
     * 
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExt.onSetFormData
     */
    p.onSetFormData = function( e, data )
    {
        var self = this;
        self.hiddenInput().val( self.serializeData( data ) );
    };

    /**
     * Reacts to `getFormData` event triggered by the core. At the bare minimum the core will tell
     * itself to use the current value in the text input as the data to be submitted with the HTML
     * form.
     *
     * @signature TextExt.onGetFormData(e, data)
     *
     * @param e {Event} jQuery event.
     *
     * @author agorbatchev
     * @date 2011/08/09
     * @id TextExt.onGetFormData
     */
    p.onGetFormData = function( e, data )
    {
        var val = this.input().val();
        data[0] = formDataObject( val, val );
    };

    //--------------------------------------------------------------------------------
    // User mouse/keyboard input

    /**
     * Triggers `[name]KeyUp` and `[name]KeyPress` for every keystroke as described in the events.
     *
     * @signature TextExt.onKeyUp(e)
     *
     * @param e {Object} jQuery event.
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.onKeyUp
     */

    /**
     * Triggers `[name]KeyDown` for every keystroke as described in the events.
     *
     * @signature TextExt.onKeyDown(e)
     *
     * @param e {Object} jQuery event.
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.onKeyDown
     */

    $( [ 'Down', 'Up' ] ).each( function()
    {
        var type = this.toString();

        p['onKey' + type] = function( e )
        {
            var self = this,
                keyName = self.opts( OPT_KEYS )[e.keyCode],
                defaultResult = true
                ;

            if ( keyName )
            {
                defaultResult = keyName.substr( -1 ) != '!';
                keyName = keyName.replace( '!', '' );

                self.trigger( keyName + 'Key' + type );

                // manual *KeyPress event fimplementation for the function keys like Enter, Backspace, etc.
                if ( type == 'Up' && self._lastKeyDown == e.keyCode )
                {
                    self._lastKeyDown = null;
                    self.trigger( keyName + 'KeyPress' );
                }

                if ( type == 'Down' )
                    self._lastKeyDown = e.keyCode;
            }

            self.trigger( 'anyKey' + type, e.keyCode );

            return defaultResult;
        };
    } );

    //--------------------------------------------------------------------------------
    // Plugin Base

    p = TextExtPlugin.prototype;

    /**
     * Allows to add multiple event handlers which will be execued in the scope of the current object.
     * 
     * @signature TextExt.on([target], handlers)
     *
     * @param target {Object} **Optional**. Target object which has traditional `bind(event, handler)` method.
     *                        Handler function will still be executed in the current object's scope.
     * @param handlers {Object} Key/value pairs of event names and handlers, eg `{ event: handler }`.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.on
     */
    p.on = hookupEvents;

    /**
     * Returns the hash object that `getFormData` triggered by the core expects.
     *
     * @signature TextExtPlugin.formDataObject(input, form)
     *
     * @param input {String} Value that will go into the text input that user is interacting with.
     * @param form {Object} Value that will be serialized and put into the hidden that will be submitted
     * with the HTML form.
     *
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExtPlugin.formDataObject
     */
    p.formDataObject = formDataObject;

    /**
     * Initialization method called by the core during plugin instantiation. This method must be implemented
     * by each plugin individually.
     *
     * @signature TextExtPlugin.init(core)
     *
     * @param core {TextExt} Instance of the TextExt core class.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.init
     */
    p.init = function( core ) {
        throw new Error( 'Not implemented' )
    };

    /**
     * Initialization method wich should be called by the plugin during the `init()` call.
     *
     * @signature TextExtPlugin.baseInit(core, defaults)
     *
     * @param core {TextExt} Instance of the TextExt core class.
     * @param defaults {Object} Default plugin options. These will be checked if desired value wasn't
     * found in the options supplied by the user.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.baseInit
     */
    p.baseInit = function( core, defaults )
    {
        var self = this;

        core._defaults = $.extend( true, core._defaults, defaults );
        self._core = core;
        self._timers = { };
    };

    /**
     * Allows starting of multiple timeout calls. Each time this method is called with the same
     * timer name, the timer is reset. This functionality is useful in cases where an action needs
     * to occur only after a certain period of inactivity. For example, making an AJAX call after 
     * user stoped typing for 1 second.
     *
     * @signature TextExtPlugin.startTimer(name, delay, callback)
     *
     * @param name {String} Timer name.
     * @param delay {Number} Delay in seconds.
     * @param callback {Function} Callback function.
     *
     * @author agorbatchev
     * @date 2011/08/25
     * @id TextExtPlugin.startTimer
     */
    p.startTimer = function( name, delay, callback )
    {
        var self = this;

        self.stopTimer( name );

        self._timers[name] = setTimeout(
            function()
            {
                delete self._timers[name];
                callback.apply( self );
            },
            delay * 1000
            );
    };

    /**
     * Stops the timer by name without resetting it.
     *
     * @signature TextExtPlugin.stopTimer(name)
     *
     * @param name {String} Timer name.
     *
     * @author agorbatchev
     * @date 2011/08/25
     * @id TextExtPlugin.stopTimer
     */
    p.stopTimer = function( name )
    {
        clearTimeout( this._timers[name] );
    };

    /**
     * Returns instance of the `TextExt` to which current instance of the plugin is attached to.
     *
     * @signature TextExtPlugin.core()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.core
     */
    p.core = function()
    {
        return this._core;
    };

    /**
     * Shortcut to the core's `opts()` method. Returns option value.
     *
     * @signature TextExtPlugin.opts(name)
     * 
     * @param name {String} Option name as described in the options.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.opts
     */
    p.opts = function( name )
    {
        return this.core().opts( name );
    };

    /**
     * Shortcut to the core's `itemManager()` method. Returns instance of the `ItemManger` that is
     * currently in use.
     *
     * @signature TextExtPlugin.itemManager()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.itemManager
     */
    p.itemManager = function()
    {
        return this.core().itemManager();
    };

    /**
     * Shortcut to the core's `input()` method. Returns instance of the HTML element that represents
     * current text input.
     *
     * @signature TextExtPlugin.input()
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.input
     */
    p.input = function()
    {
        return this.core().input();
    };

    /**
     * Shortcut to the commonly used `this.input().val()` call to get or set value of the text input.
     *
     * @signature TextExtPlugin.val(value)
     *
     * @param value {String} Optional value. If specified, the value will be set, otherwise it will be
     * returned.
     *
     * @author agorbatchev
     * @date 2011/08/20
     * @id TextExtPlugin.val
     */
    p.val = function( value )
    {
        var input = this.input();

        if ( typeof ( value ) === UNDEFINED )
            return input.val();
        else
            input.val( value );
    };

    /**
     * Shortcut to the core's `trigger()` method. Triggers specified event with arguments on the
     * component core.
     *
     * @signature TextExtPlugin.trigger(event, ...args)
     *
     * @param event {String} Name of the event to trigger.
     * @param ...args All remaining arguments will be passed to the event handler.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExtPlugin.trigger
     */
    p.trigger = function()
    {
        var core = this.core();
        core.trigger.apply( core, arguments );
    };

    /**
     * Shortcut to the core's `bind()` method. Binds specified handler to the event.
     *
     * @signature TextExtPlugin.bind(event, handler)
     *
     * @param event {String} Event name.
     * @param handler {Function} Event handler.
     *
     * @author agorbatchev
     * @date 2011/08/20
     * @id TextExtPlugin.bind
     */
    p.bind = function( event, handler )
    {
        this.core().bind( event, handler );
    };

    /**
     * Returns initialization priority for this plugin. If current plugin depends upon some other plugin
     * to be initialized before or after, priority needs to be adjusted accordingly. Plugins with higher
     * priority initialize before plugins with lower priority.
     *
     * Default initialization priority is `0`.
     *
     * @signature TextExtPlugin.initPriority()
     *
     * @author agorbatchev
     * @date 2011/08/22
     * @id TextExtPlugin.initPriority
     */
    p.initPriority = function()
    {
        return 0;
    };

    //--------------------------------------------------------------------------------
    // jQuery Integration

    /**
     * TextExt integrates as a jQuery plugin available through the `$(selector).textext(opts)` call. If
     * `opts` argument is passed, then a new instance of `TextExt` will be created for all the inputs
     * that match the `selector`. If `opts` wasn't passed and TextExt was already intantiated for 
     * inputs that match the `selector`, array of `TextExt` instances will be returned instead.
     *
     *     // will create a new instance of `TextExt` for all elements that match `.sample`
     *     $('.sample').textext({ ... });
     *
     *     // will return array of all `TextExt` instances
     *     var list = $('.sample').textext();
     *
     * The following properties are also exposed through the jQuery `$.fn.textext`:
     *
     * * `TextExt` -- `TextExt` class.
     * * `TextExtPlugin` -- `TextExtPlugin` class.
     * * `ItemManager` -- `ItemManager` class.
     * * `plugins` -- Key/value table of all registered plugins.
     * * `addPlugin(name, constructor)` -- All plugins should register themselves using this function.
     *
     * @author agorbatchev
     * @date 2011/08/19
     * @id TextExt.jquery
     */

    var cssInjected = false;

    var textext = $.fn.textext = function( opts )
    {
        var css;

        if ( !cssInjected && ( css = $.fn.textext.css ) != null )
        {
            $( 'head' ).append( '<style>' + css + '</style>' );
            cssInjected = true;
        }

        return this.map( function()
        {
            var self = $( this );

            if ( opts == null )
                return self.data( 'textext' );

            var instance = new TextExt();

            instance.init( self, opts );
            self.data( 'textext', instance );

            return instance.input()[0];
        } );
    };

    /**
     * This static function registers a new plugin which makes it available through the `plugins` option
     * to the end user. The name specified here is the name the end user would put in the `plugins` option
     * to add this plugin to a new instance of TextExt.
     * 
     * @signature $.fn.textext.addPlugin(name, constructor)
     *
     * @param name {String} Name of the plugin.
     * @param constructor {Function} Plugin constructor.
     *
     * @author agorbatchev
     * @date 2011/10/11
     * @id TextExt.addPlugin
     */
    textext.addPlugin = function( name, constructor )
    {
        textext.plugins[name] = constructor;
        constructor.prototype = new textext.TextExtPlugin();
    };

    /**
     * This static function registers a new patch which is added to each instance of TextExt. If you are
     * adding a new patch, make sure to call this method.
     * 
     * @signature $.fn.textext.addPatch(name, constructor)
     *
     * @param name {String} Name of the patch.
     * @param constructor {Function} Patch constructor.
     *
     * @author agorbatchev
     * @date 2011/10/11
     * @id TextExt.addPatch
     */
    textext.addPatch = function( name, constructor )
    {
        textext.patches[name] = constructor;
        constructor.prototype = new textext.TextExtPlugin();
    };

    textext.TextExt = TextExt;
    textext.TextExtPlugin = TextExtPlugin;
    textext.ItemManager = ItemManager;
    textext.plugins = { };
    textext.patches = { };
} )( jQuery );

( function( $ )
{
    function TextExtIE9Patches() {
    }
    ;

    $.fn.textext.TextExtIE9Patches = TextExtIE9Patches;
    $.fn.textext.addPatch( 'ie9', TextExtIE9Patches );

    var p = TextExtIE9Patches.prototype;

    p.init = function( core )
    {
        if ( navigator.userAgent.indexOf( 'MSIE 9' ) == -1 )
            return;

        var self = this;

        core.on( { postInvalidate: self.onPostInvalidate } );
    };

    p.onPostInvalidate = function()
    {
        var self = this,
            input = self.input(),
            val = input.val()
            ;

        // agorbatchev :: IE9 doesn't seem to update the padding if box-sizing is on until the
        // text box value changes, so forcing this change seems to do the trick of updating
        // IE's padding visually.
        input.val( Math.random() );
        input.val( val );
    };
} )( jQuery );

